<template>
  <div class="notify-im">
    <div class="left-concat-list">
      <ul
        class="gloab-scroll-bar"
        v-if="chatPersonList.length">
        <li
          class="linear-border-bottom"
          v-for="item in chatPersonList"
          :key="item.userId"
          :class="{ selected: item.userId === currentChatPerson?.userId, top: item.sort }"
          @click="handleClickChatPerson(item)">
          <div class="avatar">
            <HeadAvatar :width="32" :height="32" :img="item.avatar"/>
            <span class="no-read-count" v-show="item.count">{{ item.count }}</span>
          </div>
          <div class="info">
            <p class="nickname">
              <span>{{ item.nickname }}</span>
              <em class="send-time">{{ cycleImDate(item.sendTime, true) }}</em>
            </p>
            <p class="last-record">
              <span
                v-if="!item.lastChatImage"
                v-html="item.lastChatRecord?.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '<em>$&</em>')"></span>
              <span v-else>[图片]</span>
              <i
                v-if="item.sendFail"
                style="vertical-align: top;"
                class="ri-error-warning-fill send-fail"></i>
            </p>
          </div>
          <i v-if="item.sort" class="ri-pushpin-fill"></i>
        </li>
      </ul>
      <div  v-else style="margin-top: 332px;">
        <p style="color: #ccc; text-align: center;">你还没有正在聊天的好友</p>
      </div>
    </div>
    <div class="right-im-block" v-if="currentChatPerson">
      <div class="im-title">
        <p>
          <nuxt-link target="_blank" :to="`/user/${currentChatPerson.userId}`">{{ currentChatPerson.nickname }}</nuxt-link>
          <span>{{ currentChatPerson?.description }}</span>
        </p>
        <a-dropdown overlayClassName="custom-dropdown" placement="bottomCenter">
          <i class="ri-more-fill"></i>
          <a-menu slot="overlay">
            <a-menu-item>
              <p @click="handleTargetIm(currentChatPerson.sort ? 'reset-top' : 'top')">{{ currentChatPerson.sort ? '取消置顶' : '置顶消息'}}</p>
            </a-menu-item>
            <!-- <a-menu-item>
              <p>消息免打扰</p>
            </a-menu-item> -->
            <a-menu-item>
              <p @click="handleTargetIm('delete')">删除聊天</p>
            </a-menu-item>
            <a-menu-item>
              <nuxt-link target="_blank" :to="`/user/${currentChatPerson.userId}`">进入个人主页</nuxt-link>
            </a-menu-item>
          </a-menu>
        </a-dropdown>
      </div>
      <div class="im-content">
        <div class="im-record-window gloab-scroll-bar">
          <div class="im-message-list">
            <p class="request-loading" id="observe-loading" v-show="!hiddenRequestLoading">
              <i class="ri-loader-2-line"></i>
              <span>加载中</span>
            </p>
            <div
              class="im-message-list-item"
              v-for="(item, index) in chatRecordList"
              :key="item.id"
              :class="{ ar: item.fromId === userInfo.id }">
              <div
                class="im-message-title"
                v-if="!chatRecordList[index - 1]
                || item.sendTime !== chatRecordList[index - 1].sendTime">{{ item.sendTime }}</div>
              <div v-if="!item.sys" class="im-message-content">
                <HeadAvatar
                  :width="32"
                  :height="32"
                  :img="item.fromId === userInfo.id ? userInfo.avatar : currentChatPerson.avatar"/>
                <div class="container">
                  <i
                    v-if="(item.fromId === userInfo.id) && item.sendIng"
                    class="ri-loader-2-line loading"></i>
                  <i
                    v-if="(item.fromId === userInfo.id) && item.sendFail"
                    class="ri-error-warning-fill send-fail"></i>
                  <div
                    v-if="!item.image"
                    class="record-message"
                    v-html="item.showContent"
                    @contextmenu="handleMessageRightClick($event, item)"></div>
                  <div
                    v-else
                    @contextmenu="handleMessageRightClick($event, item)"
                    class="record-image"
                    :style="{ width: item.imageWidth + 'px', height: item.imageHeight + 'px' }">
                    <img :src="item.image" alt="image">
                  </div>
                </div>
                <div class="quote-message" v-if="item.quoteId">
                  <div class="text">
                    <p class="nickanme">{{ item.quoteNickname }}：</p>
                    <img v-if="item.quoteImage" :src="item.quoteImage" alt="">
                    <p v-else class="mes">{{ item.quoteText }}</p>
                  </div>
                </div>
              </div>
              <!-- 系统消息 -->
              <div v-else class="sys-im-message-content">
                <p>”{{ item.fromId === userInfo.id ? '你' : currentChatPerson.nickname }}“ 撤回了一条消息</p>
              </div>
              <!-- 消息发送错误提示 -->
              <div class="error-message-tip" v-if="item.failMessage">
                <p>{{ item.failMessage }}</p>
              </div>
            </div>
          </div>
        </div>
        <div class="load-more-message" v-if="noReadCountTotal" @click="chatWindowScrollToBottom">
          <i class="ri-arrow-down-double-line"></i>
          <span>{{ noReadCountTotal }}条新消息</span>
        </div>
      </div>
      <div class="im-send-block">
        <div class="top">
          <EmojiPicker :showText="false" @select="selectMessageEmoji" />
          <a-upload
            name="file"
            action="https://image.zdxblog.cn/upload/uploadFile"
            accept="image/*"
            :showUploadList="false"
            @change="handleUploadImageChange"
          >
            <p class="select-pic">
              <i class="ri-folder-image-line"></i>
            </p>
          </a-upload>
        </div>
        <div class="center">
          <div class="quote-content" v-if="quoteContent">
            <p class="nickname">{{ quoteContent.quoteNickname }}：</p>
            <div class="content">
              <img v-if="quoteContent.quoteImage" :src="quoteContent.quoteImage" alt="quoteImage">
              <p v-else class="text">{{ quoteContent.quoteText }}</p>
            </div>
            <i @click="quoteContent = null" class="ri-close-circle-fill close"></i>
          </div>
          <textarea
            v-model="imcontent"
            autofocus
            :maxlength="500"
            id="im-textarea"
            class="gloab-scroll-bar"></textarea>
        </div>
        <div class="bottom">
          <div class="max-tip">{{ imcontent.length }}/500</div>
          <div class="send-tip">
            按下Enter发送内容
            <button :disabled="!imcontent.replace(/\s/g, '')" @click="handleClickSend">发送</button>
          </div>
        </div>
      </div>
    </div>
    <div class="right-empty" v-else>
      <p>您还未选中或者发起聊天，快去跟好友聊一聊吧</p>
    </div>
    <ContextMenu
      :commandList="commandList"
      :contextPosition="contextPosition"
      @command-click="handleContextClick" />
  </div>
</template>

<script>
import { mapState } from 'vuex'
import { dateFormat, new15RandomId, cycleImDate, getImageSize, resetLocationSearchURL } from '@/util'
import ContextMenu from '~/components/ContextMenu'

export default {
  name: 'NotifyIm',
  components: { ContextMenu },
  data () {
    return {
      chatPersonList: [],
      chatRecordList: [],
      imcontent: '',
      currentChatPerson: null,
      pagination: {
        pageNo: 1,
        pageSize: 15,
        total: 0,
        loading: false
      },
      hiddenRequestLoading: false,
      observer: null,
      noReadCountTotal: 0,
      contextPosition: null,
      quoteContent: null
    }
  },
  head () {
    return {
      title: '私信消息-Gimishare'
    }
  },

  mounted () {
    this.queryChatImWithMe()
    this.bindIntersectionObserver()
  },

  methods: {
    handleClickSend () {
      if (!this.imcontent.replace(/\s/g, '')) { return }
      const sendMessageContent = {
        fromId: this.userInfo.id,
        toId: this.currentChatPerson.userId,
        content: this.imcontent,
        sendTime: dateFormat(new Date()),
        sendTimeStamp: new Date().getTime(),
        id: new15RandomId(),
        quoteContent: this.quoteContent
      }
      this.chatImSendMessage(sendMessageContent)
    },

    /**
     * websocket 消息处理并且发送信息
     * @param {*} sendMessageContent
     * @param {*} writeSuccess
     */
    chatImSendMessage (sendMessageContent, writeSuccess = true) {
      const imsocket = this.$store.state.imsocket
      const { content, sendTime, image, quoteContent } = sendMessageContent
      if (imsocket && imsocket.readyState === 1 && writeSuccess) {
        imsocket.send(JSON.stringify(sendMessageContent))
        this.chatRecordList.push({
          ...sendMessageContent,
          sendTime: cycleImDate(sendMessageContent.sendTime),
          showContent: sendMessageContent.content
            ?.replace(/https?:\/\/[^\s]+/g, "<a href='$&' target='_blank'>$&</a>")
            .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '<em>$&</em>'),
          ...(quoteContent || {}),
          sendIng: true
        })
        this.updateTargetImInfo(this.currentChatPerson.userId, { content, sendTime, image })
      } else {
        this.chatRecordList.push({
          ...sendMessageContent,
          ...(quoteContent || {}),
          sendTime: cycleImDate(sendMessageContent.sendTime),
          showContent: sendMessageContent.content
            ?.replace(/https?:\/\/[^\s]+/g, "<a href='$&' target='_blank'>$&</a>")
            .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '<em>$&</em>'),
          sendFail: true
        })
        this.$set(this.currentChatPerson, 'sendFail', true)
        writeSuccess && this.$message.error('网络连接已断开，请刷新页面重新连接')
        this.updateTargetImInfo(this.currentChatPerson.userId, { content, image, sendTime, sendFail: true })
      }
      this.currentChatPerson.lastChatRecord = this.imcontent
      this.currentChatPerson.lastChatImage = sendMessageContent.image
      this.imcontent = ''
      this.quoteContent = null
      this.chatWindowScrollToBottom()
      this.sortChatWindowIm(this.currentChatPerson.userId)
    },

    /**
     * 查询同我聊天的所有用户列表
     * @param {*} onlySort true的时候只做排序更新不需要请求该用户的聊天记录
     */
    async queryChatImWithMe (onlySort = false) {
      let selectIdx = 0
      const chatPersons = await this.$axios.post('/gimishare/api/v1/im/chat-person-list')
      const chatPersonList = chatPersons.data || []
      const participantId = this.currentChatPerson?.userId || this.$route.query.participantId
      if (participantId) {
        const idx = chatPersonList.findIndex(o => o.userId === participantId)
        if (idx === -1) {
          const userInfo = await this.getUserInfoById(participantId)
          const { id, nickname, avatar, description } = userInfo
          const ii = chatPersonList.findLastIndex(o => o.sort)
          chatPersonList.splice(ii + 1, 0, {
            userId: id, nickname, avatar, description
          })
          this.hiddenRequestLoading = true
          selectIdx = ii + 1
        } else {
          selectIdx = idx
        }
      }
      this.chatPersonList = chatPersonList
      this.currentChatPerson = this.chatPersonList[selectIdx]
      if (onlySort) return
      if (this.chatPersonList.length) {
        this.$nextTick(() => {
          this.findChatRecordList()
          this.bindChatWindowScrollEvent()
        })
        if (this.currentChatPerson.count) {
          setTimeout(() => {
            this.currentChatPerson.count = 0
          }, 1000)
        }
      }
    },

    /**
     * 根据用户id查询与该用户的聊天记录
     */
    async findChatRecordList () {
      try {
        const observeLoading = document.querySelector('#observe-loading')
        observeLoading && this.observer.unobserve(observeLoading)
        const searchParams = {
          toId: this.currentChatPerson.userId,
          pageNo: this.pagination.pageNo,
          pageSize: this.pagination.pageSize
        }
        const { data } = await this.$axios.post('/gimishare/api/v1/im/chat-record-list', searchParams)
        this.pagination.total = data.total
        this.chatRecordList.unshift(...data.list.map(o => {
          return {
            ...o,
            showContent: o.content
              ?.replace(/https?:\/\/[^\s]+/g, "<a href='$&' target='_blank'>$&</a>")
              .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '<em>$&</em>'),
            sendTime: cycleImDate(o.sendTime)
          }
        }))
        if (this.pagination.pageNo === 1) {
          this.chatWindowScrollToBottom()
          // 将消息标记为已读
          const noReadMessageList = data.list.filter(o => o.fromId === searchParams.toId && o.receiverRead === '0')
          if (noReadMessageList.length) {
            const idx = noReadMessageList.length - 1
            this.batchReadMessage(this.currentChatPerson.userId, noReadMessageList[idx].sendTimeStamp)
          }
        } else {
          // 在第一页之后请求数据渲染后将滚动条同步增加达到无缝加载效果
          this.$nextTick(() => {
            let totalHeight = 0
            const messageListNodes = document.querySelectorAll('.im-message-list-item')
            for (let i = 0; i < data.list.length; i++) {
              totalHeight += (messageListNodes[i].clientHeight + 20)
            }
            const scrollNode = document.querySelector('.im-record-window')
            scrollNode.scrollTop = scrollNode.scrollTop + totalHeight - 20
          })
        }
        this.hiddenRequestLoading = this.chatRecordList.length === data.total
        if (this.hiddenRequestLoading) {
          const observeLoading = document.querySelector('#observe-loading')
          this.observer.unobserve(observeLoading)
        } else {
          const observeLoading = document.querySelector('#observe-loading')
          this.observer.observe(observeLoading)
        }
      } catch (error) {
        console.log(error)
      }
    },

    /**
     * 撤回消息
     */
    async revocateImRecord () {
      const imId = this.contextPosition.id
      try {
        await this.$axios.get(`/gimishare/api/v1/im/revocate-im?imId=${imId}`)
        this.staticDeleteImRecord(imId)
      } catch (error) {
        console.log(error)
      }
    },

    /**
     * 删除指定的聊天记录
     */
    async deleteImRecord () {
      this.$confirm({
        title: '删除提示',
        content: '删除聊天记录后无法恢复，确认删除？',
        okText: '删除',
        cancelText: '取消',
        okType: 'danger',
        onOk: async () => {
          try {
            const imId = this.contextPosition.id
            await this.$axios.post('/gimishare/api/v1/im/delete-im-record', { imId })
            this.staticDeleteImRecord(imId)
          } catch (error) {
            console.log(error)
          }
        }
      })
    },

    /**
     * 删除指定聊天人包括其所有聊天记录
     */
    async deleteChatImPerson () {
      this.$confirm({
        title: '删除提示',
        content: '删除聊天后会删除与该用户的所有聊天记录，并且不可恢复，确认删除？',
        okText: '删除',
        cancelText: '取消',
        okType: 'danger',
        onOk: async () => {
          try {
            const userId = this.currentChatPerson.userId
            await this.$axios.post(`/gimishare/api/v1/im/delete-im-person`, { toId: userId })
            const idx = this.chatPersonList.findIndex(o => o.userId === userId)
            if (idx > -1) {
              this.chatPersonList.splice(idx, 1)
              this.currentChatPerson = null
              resetLocationSearchURL()
            }
          } catch (error) {
            console.log(error)
          }
        }
      })
    },

    staticDeleteImRecord (imId) {
      const idx = this.chatRecordList.findIndex(o => o.id === imId)
      if (idx > -1) {
        this.chatRecordList.splice(idx, 1)
        const L = this.chatRecordList.length
        if (idx === this.chatRecordList.length) {
          this.currentChatPerson.lastChatImage = this.chatRecordList[L - 1]?.image
          this.currentChatPerson.lastChatRecord = this.chatRecordList[L - 1]?.content
        }
      }
    },

    handleClickChatPerson (personItem) {
      if (personItem.userId === this.currentChatPerson?.userId) { return }
      const observeLoading = document.querySelector('#observe-loading')
      observeLoading && this.observer.unobserve(observeLoading)
      personItem.count = 0
      this.hiddenRequestLoading = false
      this.pagination.pageNo = 1
      this.chatRecordList = []
      this.currentChatPerson = personItem
      resetLocationSearchURL({ participantId: personItem.userId })
      this.findChatRecordList()
      this.statisticNoReadImCount()
    },

    async handleTargetIm (type) {
      try {
        if (type === 'top') {
          await this.$axios.get(`/gimishare/api/v1/im/setTop?toId=${this.currentChatPerson.userId}`)
          this.queryChatImWithMe(true)
        }
        if (type === 'reset-top') {
          await this.$axios.get(`/gimishare/api/v1/im/resetTop?toId=${this.currentChatPerson.userId}`)
          this.queryChatImWithMe(true)
        }
        if (type === 'delete') {
          this.deleteChatImPerson()
        }
      } catch (error) {
        console.log(error)
      }
    },

    handleMessageRightClick (e, item) {
      e.preventDefault()
      this.contextPosition = {
        left: e.pageX,
        top: e.pageY,
        ...item
      }
    },

    handleContextClick (command) {
      if (command === 'delete') {
        this.deleteImRecord()
      } else if (command === 'copy') {
        navigator.clipboard.writeText(this.contextPosition.content)
      } else if (command === 'revocate') {
        this.revocateImRecord()
      } else if (command === 'quote') {
        const { id, content, image, fromId } = this.contextPosition
        this.quoteContent = {
          quoteId: id,
          quoteText: content,
          quoteImage: image,
          quoteNickname: fromId === this.userInfo.id ? this.userInfo.nickname : this.currentChatPerson.nickname
        }
      }
    },

    /**
     * 聊天窗口的联调记录滚动到最底部
     */
    chatWindowScrollToBottom () {
      this.$nextTick(() => {
        const recordDocument = document.querySelector('.im-message-list')
        if (!recordDocument) { return }
        const documentHeight = recordDocument.clientHeight
        const chatWindow = document.querySelector('.im-record-window')
        const windowHeight = chatWindow.clientHeight
        chatWindow.scrollTop = documentHeight - windowHeight + 20
      })
    },

    /**
     * 检测当前的聊天窗口是不是在最底部(允许16px误差)
     */
    isToLatBottom () {
      const recordDocument = document.querySelector('.im-message-list')
      const documentHeight = recordDocument.clientHeight
      const chatWindow = document.querySelector('.im-record-window')
      const windowHeight = chatWindow.clientHeight
      return chatWindow.scrollTop >= documentHeight - windowHeight - 16
    },

    /**
     * 根据好友的userId把当前的聊天框置于所有置顶消息后的第一个位置
     * @param {*} userId
     */
    sortChatWindowIm (userId) {
      const idx = this.chatPersonList.findIndex(o => o.userId === userId)
      if (idx > 0) {
        if (this.chatPersonList[idx].sort) return
        const t = this.chatPersonList.splice(idx, 1)
        const ii = this.chatPersonList.findLastIndex(o => o.sort)
        this.chatPersonList.splice(ii + 1, 0, t[0])
      }
    },

    bindChatWindowScrollEvent () {
      const chatImWindow = document.querySelector('.im-record-window')
      chatImWindow.onscroll = () => {
        // 监听滚动到最底部，如果存在未读消息条数将未读消息条数重置为0
        if (this.isToLatBottom() && this.noReadCountTotal) {
          this.noReadCountTotal = 0
        }
      }
    },

    /**
     * 根据用户id更新指定聊天成员的信息
     * @param {*} userId
     * @param {*} message
     */
    updateTargetImInfo (userId, message) {
      const target = this.chatPersonList.find(o => o.userId === userId)
      const { content, sendTime, image, sendFail = false } = message
      if (target) {
        target.lastChatRecord = content
        target.lastChatImage = image
        target.sendTime = sendTime
        this.$set(target, 'sendFail', sendFail)
      }
    },

    /**
     * select emoji
     * @param { Object } emoji
     */
    selectMessageEmoji (emoji) {
      const input = document.getElementById('im-textarea')
      const startPos = input.selectionStart
      const endPos = input.selectionEnd
      const resultText = input.value.substring(0, startPos) + emoji.data + input.value.substring(endPos)
      input.value = resultText
      input.focus()
      input.selectionStart = startPos + emoji.data.length
      input.selectionEnd = startPos + emoji.data.length
      this.imcontent = resultText
    },

    setEnterQuickCommand (e) {
      if (e.target.id === 'im-textarea') {
        const ec = e.keyCode || e.which
        if (!e.ctrlKey && ec === 13) {
          this.handleClickSend()
          e.preventDefault()
        }
        if (e.ctrlKey && ec === 13) {
          const currentCursorPosition = e.target.selectionStart
          this.imcontent = this.imcontent.substring(0, currentCursorPosition) + '\n' + this.imcontent.substring(e.target.selectionEnd)
          this.$nextTick(() => {
            e.target.selectionStart = currentCursorPosition + 1
            e.target.selectionEnd = currentCursorPosition + 1
          })
          e.preventDefault()
        }
      }
    },

    bindIntersectionObserver () {
      this.observer = new IntersectionObserver((entries, observer) => {
        if (entries[0].isIntersecting) {
          this.pagination.pageNo++
          this.findChatRecordList()
        }
      }, { threshold: 1 })
      document.documentElement.addEventListener('keydown', this.setEnterQuickCommand)
    },

    async handleUploadImageChange (info) {
      if (info.file.status === 'done') {
        const uploadSuccess = info.file.response.code === '0'
        const size = await getImageSize(info.file.originFileObj)
        const width = size.width <= 280 ? size.width : 280
        const height = size.height / Math.max(size.width / 280, 1)
        const sendImageMessage = {
          fromId: this.userInfo.id,
          toId: this.currentChatPerson.userId,
          content: null,
          sendTime: dateFormat(new Date()),
          sendTimeStamp: new Date().getTime(),
          id: new15RandomId(),
          image: uploadSuccess ? info.file.response.data : URL.createObjectURL(info.file.originFileObj),
          imageWidth: width,
          imageHeight: height,
          quoteContent: this.quoteContent
        }
        this.chatImSendMessage(sendImageMessage, uploadSuccess)
      }
    },

    async batchReadMessage (fromId, sendTimeStamp) {
      await this.$axios.post('/gimishare/api/v1/im/read-message', {
        fromId,
        sendTimeStamp
      })
    },

    async getUserInfoById (userId) {
      try {
        const { data } = await this.$axios.get(`/gimishare/api/v1/users/userinfo?id=${userId}`)
        return data
      } catch (error) {
        return { id: userId }
      }
    },

    statisticNoReadImCount () {
      const countObject = {
        ...this.notifyCount,
        imCount: this.chatPersonList.reduce((prev, cur) => {
          prev += cur.count
          return prev
        }, 0)
      }
      this.$store.commit('UPDATE_NOTIFY_COUNT', countObject)
    },

    cycleImDate
  },

  watch: {
    imMessage: {
      async handler (newVal) {
        if (newVal) {
          const message = JSON.parse(newVal)
          // 发送消息后返回消息发送结果
          if (message.sendResultcallback) {
            const target = this.chatRecordList.find(o => o.id === message.id)
            target.sendIng = false
            target.sendFail = !message.sendSuccessFlag
            if (message.failMessage) {
              this.$set(target, 'failMessage', message.failMessage)
              this.chatWindowScrollToBottom()
            }
            this.$set(this.currentChatPerson, 'sendFail', !message.sendSuccessFlag)
          } else {
            // 接收消息
            // 接收系统消息
            const { content, image, sendTime, sendTimeStamp, sys } = message
            if (sys) {
              this.chatRecordList.push({
                ...message,
                showContent: content,
                sendTime: cycleImDate(sendTime)
              })
              const userId = message.fromId === this.userInfo.id ? message.toId : message.fromId
              if (message.fromId !== this.userInfo.id) {
                this.staticDeleteImRecord(message.deleteImId)
              }
              this.updateTargetImInfo(userId, { content, sendTime })
              return
            }
            // 如果是当前正在聊天的用户直接把信息显示在聊天框中
            if (this.currentChatPerson?.userId === message.fromId) {
              this.chatRecordList.push({
                ...message,
                showContent: content
                  ?.replace(/https?:\/\/[^\s]+/g, "<a href='$&' target='_blank'>$&</a>")
                  .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '<em>$&</em>'),
                sendTime: cycleImDate(sendTime)
              })
              if (this.isToLatBottom()) {
                this.chatWindowScrollToBottom()
              } else {
                this.noReadCountTotal += 1
              }
              // 只要在当前聊天窗口收到的消息都变成已读消息
              this.batchReadMessage(message.fromId, sendTimeStamp)
            } else {
              const target = this.chatPersonList.find(o => o.userId === message.fromId)
              // 当前聊天成员里面没有该消息发送人时
              if (!target) {
                const userInfo = await this.getUserInfoById(message.fromId)
                const { id, nickname, avatar, description } = userInfo
                this.chatPersonList.unshift({
                  userId: id, nickname, avatar, description, lastChatRecord: content, lastChatImage: image, sendTime, count: 1
                })
                return
              } else {
                target.count += 1
              }
            }
            this.updateTargetImInfo(message.fromId, { content, sendTime, image })
            this.sortChatWindowIm(message.fromId)
          }
        }
      },
      deep: true
    },

    currentChatPerson: {
      handler (newVal) {
        if (newVal) {
          this.imcontent = ''
          this.$nextTick(() => {
            const inputTextarea = document.querySelector('#im-textarea')
            inputTextarea.focus()
          })
        }
      },
      deep: true
    }
  },

  computed: {
    ...mapState({
      userInfo: state => state.userInfo,
      imMessage: state => state.imMessage,
      notifyCount: state => state.notifyCount
    }),

    commandList () {
      if (!this.contextPosition) return []
      const { fromId, image, sendTimeStamp } = this.contextPosition
      const last2Minutes = new Date().getTime() - sendTimeStamp <= 2 * 60 * 1000
      return [
        { name: '复制', command: 'copy', hidden: !!image },
        { name: '撤回', command: 'revocate', hidden: fromId !== this.userInfo.id || !last2Minutes },
        { name: '引用', command: 'quote' },
        { name: '删除', command: 'delete', hidden: last2Minutes && fromId === this.userInfo.id }
      ].filter(o => Boolean(!o.hidden))
    }
  },

  beforeDestroy () {
    document.documentElement.removeEventListener('keydown', this.setEnterQuickCommand)
    this.observer && this.observer.disconnect()
    // 离开界面的时候重新计算所有未读消息总数
    this.statisticNoReadImCount()
  }
}
</script>
<style lang="less" scoped>
.notify-im {
  background: #fff;
  margin-top: 12px;
  height: 706px;
  display: flex;
  border-radius: 6px;
  overflow: hidden;
  .left-concat-list {
    width: 290px;
    border-right: 1px solid #f1f1f1;
    display: flex;
    flex-direction: column;
    ul {
      padding: 0;
      list-style: none;
      overflow-y: overlay;
      height: 0;
      flex: 1;
      margin: 0;
      li {
        display: flex;
        padding: 12px 10px;
        position: relative;
        cursor: pointer;
        border-radius: 0 2px 2px 0;
        &.top {
          background-color: #fafafa;
          .ri-pushpin-fill {
            position: absolute;
            right: 0px;
            top: -3px;
            font-size: 12px;
            color: #E4E6EB;
          }
        }
        &:hover {
          background-color: #f7f8fa;
        }
        &.selected {
          background-color: #eaf2ff;
        }
        &::after {
          background: linear-gradient(90deg, transparent 0%, #f1f1f1 50%, transparent 100%);
          opacity: 1;
        }
        .avatar {
          display: flex;
          align-items: center;
          position: relative;
          .no-read-count {
            position: absolute;
            background: #de0e19;
            color: #fff;
            font-size: 10px;
            border-radius: 50%;
            top: -2px;
            display: inline-block;
            width: 15px;
            height: 15px;
            line-height: 15px;
            text-align: center;
            right: -5px;
            font-weight: bold;
            padding-left: 1px;
          }
        }
        .info {
          flex: 1;
          width: 0;
          margin-left: 10px;
          .nickname {
            color: #000;
            font-size: 14px;
            display: flex;
            justify-content: space-between;
            span {
              width: 0;
              flex: 1;
              white-space: nowrap;
              overflow: hidden;
              text-overflow: ellipsis;
            }
            .send-time {
              font-size: 12px;
              color: #8a919f;
              flex-shrink: 0;
              font-style: normal;
            }
          }
          .last-record {
            font-size: 12px;
            color: #555666;
            margin-top: 2px;
            height: 20px;
            line-height: 20px;
            span {
              max-width: ~"calc(100% - 20px)";
              overflow: hidden;
              white-space: nowrap;
              text-overflow: ellipsis;
              display: inline-block;
              ::v-deep em {
                font-style: normal;
                font-size: 15px;
              }
            }
          }
        }
      }
    }
  }
  .right-im-block {
    flex: 1;
    width: 0;
    display: flex;
    flex-direction: column;
    .im-title {
      padding: 0 20px;
      display: flex;
      border-bottom: 1px solid #f1f1f1;
      height: 50px;
      align-items: center;
      justify-content: space-between;
      p {
        font-size: 16px;
        a {
          color: #222226;
        }
        span {
          font-size: 12px;
          color: #8a919f;
          margin-left: 6px;
        }
      }
      .ri-more-fill {
        font-size: 16px;
        cursor: pointer;
      }
    }
    .im-content {
      flex: 1;
      height: 0;
      padding-right: 4px;
      padding: 12px 6px 12px 18px;
      position: relative;
      .load-more-message {
        position: absolute;
        right: 10px;
        bottom: 10px;
        background: #fff;
        box-shadow: 0 0 10px 2px rgba(0,0,0,0.1);
        border-radius: 15px;
        padding: 2px 12px;
        display: flex;
        align-items: center;
        cursor: pointer;
        color: var(--primary-color);
        span {
          font-size: 12px;
        }
        i {
          font-size: 16px;
        }
      }
      .im-record-window {
        height: 100%;
        overflow-y: scroll;
        .im-message-list {
          .request-loading {
            margin-bottom: 20px;
            color: #999;
            display: flex;
            align-items: center;
            justify-content: center;
            i {
              font-size: 14px;
              display: inline-block;
              animation: loading 1.2s linear infinite;
            }
            span {
              font-size: 10px;
              margin-left: 5px;
            }
          }
          &-item {
            &:not(:nth-of-type(1)) {
              margin-top: 20px;
            }
            .im-message-title {
              text-align: center;
              font-size: 13px;
              color: #8a919f;
              margin-bottom: 12px;
            }
            .im-message-content {
              display: flex;
              align-items: flex-start;
              flex-wrap: wrap;
              .container {
                display: flex;
                max-width: 90%;
                align-items: center;
                .record-message {
                  margin-left: 16px;
                  margin-right: 6px;
                  padding: 8px 12px;
                  background: #f5f5f5;
                  border-radius: 4px;
                  color: #1c1c28;
                  white-space: pre-wrap;
                  position: relative;
                  line-height: 28px;
                  &:after {
                    content: "";
                    width: 0;
                    height: 0;
                    border-top: 6px solid transparent;
                    border-left: 6px solid transparent;
                    border-bottom: 6px solid transparent;
                    border-right: 6px solid #f5f5f5;
                    position: absolute;
                    top: 10px;
                    left: -12px;
                  }
                  ::v-deep {
                    a {
                      font-size: 15px;
                    }
                    em {
                      font-style: normal;
                      font-size: 18px;
                    }
                  }
                }
                .record-image {
                  margin: 0 12px;
                  border-radius: 4px;
                  overflow: hidden;
                  img {
                    display: block;
                    max-width: 100%;
                  }
                }
                .loading {
                  font-size: 16px;
                  animation: loading 1.6s linear infinite;
                }
              }
              .quote-message {
                width: 100%;
                padding: 0 48px;
                margin-top: 4px;
                .text {
                  display: inline-block;
                  background: #ededed;
                  font-size: 11px;
                  padding: 4px 12px 2px;
                  border-radius: 4px;
                  text-align: left;
                  .nickanme {
                    color: #4c4c4c;
                  }
                  .mes {
                    color: #999;
                    white-space: pre-wrap;
                  }
                  img {
                    display: block;
                    height: 36px;
                  }
                }
              }
            }
            .sys-im-message-content, .error-message-tip {
              p {
                font-size: 12px;
                color: #ccc;
                text-align: center;
              }
            }
            &.ar {
              padding-right: 6px;
              .im-message-content {
                flex-direction: row-reverse;
                .container {
                  .record-message {
                    background: #c9e7ff;
                    margin-right: 16px;
                    margin-left: 6px;
                    &::after {
                      border-right-color: transparent;
                      border-left-color: #c9e7ff;
                      right: -12px;
                      left: auto;
                    }
                  }
                }
                .quote-message {
                  text-align: right;
                  .text {
                    background: #f1f1f1;
                  }
                }
              }
            }
          }
        }
      }
    }
    .im-send-block {
      height: 180px;
      border-top: 1px solid #f1f1f1;
      display: flex;
      flex-direction: column;
      .top {
        display: flex;
        align-items: center;
        padding: 6px 10px 0;
        ::v-deep .custom-emoji-picker {
          i {
            color: #999;
            font-size: 21px;
          }
        }
        .select-pic {
          margin-left: 16px;
          cursor: pointer;
          i {
            font-size: 22px;
            color: #999;
          }
        }
      }
      .center {
        flex: 1;
        height: 0;
        display: flex;
        flex-direction: column;
        padding: 0 12px;
        .quote-content {
          padding: 6px 12px;
          background: #fafafa;
          border-radius: 2px;
          font-size: 11px;
          position: relative;
          &:after {
            content: "";
            width: 3px;
            top: 10px;
            bottom: 10px;
            left: 0;
            background: #ececec;
            position: absolute;
          }
          .nickname {
            color: #4c4c4c;
          }
          .text {
            color: #999;
            display: -webkit-box;
            overflow: hidden;
            text-overflow: ellipsis;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            white-space: pre-wrap;
          }
          img {
            display: block;
            max-height: 30px;
          }
          .close {
            position: absolute;
            right: -6px;
            top: -12px;
            color: #ccc;
            font-size: 16px;
            cursor: pointer;
          }
        }
        #im-textarea {
          height: 0;
          flex: 1;
          overflow-y: scroll;
          border: none;
          width: 100%;
          outline: none;
          font-size: 15px;
          color: #000;
        }
      }
      .bottom {
        display: flex;
        justify-content: space-between;
        color: #8a919f;
        font-size: 12px;
        padding: 6px 12px;
        align-items: flex-end;
        .max-tip {
          line-height: 30px;
        }
        .send-tip {
          button {
            border: none;
            outline: none;
            color: #fff;
            background: var(--primary-color);
            border-radius: 4px;
            padding: 6px 18px;
            margin-left: 8px;
            cursor: pointer;
            transition: .3s;
            &:active {
              opacity: .6;
            }
            &:disabled {
              opacity: .6;
              cursor: not-allowed;
            }
          }
        }
      }
    }
  }
  .right-empty {
    flex: 1;
    width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    p {
      font-size: 14px;
      color: #ccc;
    }
  }
  .send-fail {
    color: #d31818;
    cursor: pointer;
    font-size: 16px;
  }
}
@keyframes loading {
  from {
    transform: rotate(0deg);
  }
  from {
    transform: rotate(-360deg);
  }
}
</style>
